home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / MaskEngine.java < prev    next >
Text File  |  1998-10-16  |  17KB  |  492 lines

  1. /*
  2.   Engine class for support of masked input in IRAD.
  3.  
  4.   Created 3/26/98 by Paul Lancaster.
  5. */
  6.  
  7. package com.symantec.itools.swing;
  8.  
  9. import java.awt.event.KeyEvent;
  10. import java.awt.*;
  11.  
  12. public class MaskEngine {
  13.  
  14.   // These constants are used to specify the data type information needed by
  15.   // the engine to specialize mask behaviour.
  16.   public static final int texttype = 0;
  17.   public static final int numbtype = 1;
  18.   public static final int datetype = 2;
  19.   public static final int timetype = 3;
  20.  
  21.   public MaskEngine(String mask, int datatype) {  // ctor
  22.     init();
  23.     setMask(mask);
  24.     setDatatype(datatype);
  25.   }
  26.  
  27.   public MaskEngine() {  // default constructor
  28.     init();
  29.   }
  30.  
  31.   public void setMask(String mask) {
  32.     //vasu start
  33.     if(mask.trim().equals("")) return;
  34.     //vasu end
  35.     _mask = mask;
  36.     _maskLength = mask.length();
  37.  
  38.     // Analyze filter and initialize supporting data structures
  39.     _filterCount = 0;
  40.     resetMaskScan();
  41.  
  42.     // First pass: count filters and total displayable mask characters.
  43.     // Also mark first and last filter positions.
  44.     for (_maskCount = 0; 0 != getNextMaskChar(); _maskCount++) {
  45.       if (_curMaskType == filtertype) {
  46.         _filterCount++;                // count filters
  47.         _lastFilterPos = _curMaskPos;  // new "last" filter pos
  48.       }
  49.       if (1 == _filterCount)
  50.         _firstFilterPos = _curMaskPos; // note first filter pos
  51.     }
  52.     if (_maskCount * _filterCount == 0)
  53.       return;  // error: no displayable characters or no filters in mask
  54.  
  55.     // Second pass: fill filter position array.
  56.     _filterPositions = new int[_filterCount];
  57.     _commandCorrections = new int[_filterCount];
  58.     resetMaskScan();
  59.     for (int i = 0, j = 0; i < _maskCount; i++) {
  60.       getNextMaskChar();
  61.       if (_curMaskType == filtertype) {
  62.         _filterPositions[j] = _curMaskPos;
  63.         _commandCorrections[j++] = _commandCorrection;
  64.       }
  65.     }
  66.   }
  67.  
  68.   public void   setDatatype(int datatype) { _datatype = datatype; }
  69.   public String getMask()                 { return _mask        ; }
  70.   public int    getDatatype()             { return _datatype    ; }
  71.  
  72.   /*  Called to initialize the display of the masked data.
  73.       The first parameter is the current data in the field.
  74.       The second parameter holds the string that should be displayed.
  75.       The return value is the initial caret position.
  76.   */
  77.   public int initDisplay(String data, StringBuffer newData) {
  78.     //vasu start
  79.     if (_filterCount == 0){       // no filters?
  80.       newData.append(data);
  81.       return 0;                  // return first position
  82.     }
  83.     //vasu end
  84.     int datalen = data.length();
  85.     int inpos = 0;   // track input position
  86.     boolean zerofill           = false;
  87.     boolean dataRightOfDecimal = false;
  88.     boolean maskRightOfDecimal = false;
  89.     resetMaskScan();
  90.     char c;
  91.     for (int i = 0; (c = getNextMaskChar()) != 0; i++) { // mask drives output
  92.       if (_curMaskType == filtertype) {         // we are on a filter?
  93.         char z = zerofill ? '0' : '_';
  94.         if (inpos < datalen) {       // consumed all data yet?
  95.           c = data.charAt(inpos++);  // if not, get next data char
  96.           if (numbtype == _datatype) { // special handling for numerics
  97.             if (_decimalPoint == c) {
  98.               c = z;
  99.               dataRightOfDecimal = true;
  100.               if (maskRightOfDecimal && inpos < datalen)
  101.                 c = data.charAt(inpos++);
  102.             } else if (dataRightOfDecimal && !maskRightOfDecimal) {
  103.               c = z;
  104.               inpos--;  // can not use this fractional digit yet
  105.             } else if (!dataRightOfDecimal && maskRightOfDecimal) {
  106.               c = z;
  107.               while (inpos < datalen)    // scan data for dec. pt.
  108.                 if (_decimalPoint == data.charAt(inpos++)) {
  109.                   if (inpos < datalen)
  110.                     c = data.charAt(inpos++);
  111.                   dataRightOfDecimal = true;
  112.                   break;
  113.                 }
  114.             }
  115.           }  // if numeric field
  116.         } else            // we have consumed all input data
  117.           c = z;
  118.       } else if (_curMaskChar == _decimalPoint && numbtype == _datatype) {
  119.         maskRightOfDecimal = true;
  120.         if (0 == datalen) {  // for empty numeric fields, start zero filling
  121.           zerofill = true;
  122.           newData.setCharAt(newData.length() - 1, '0'); // zero units digit
  123.         }
  124.         else if (inpos < datalen && data.charAt(inpos) == _decimalPoint) {
  125.           inpos++;   // skip decimal point in input
  126.           dataRightOfDecimal = true;
  127.         }
  128.       }
  129.       newData.append(c);
  130.     }
  131.  
  132.     // Zero units digit of empty numeric fields with no decimal.
  133.     if (numbtype == _datatype && 0 == datalen && !zerofill && _maskLength > 0)
  134.       newData.setCharAt(newData.length() - 1, '0');
  135.  
  136.     return _filterPositions[0] - _commandCorrections[0];
  137.   }
  138.  
  139.   /* This is the main workhorse method.
  140.      It is called for every key stroke corresponding to displayable
  141.      characters once editing begins.
  142.      The 1st parameter is the user keystroke event object.
  143.      The 2nd parameter is the current cursor position (zero based).
  144.      The 3rd parameter is the current text from the component.
  145.      The 4th parameter is output and is what should be displayed in the component.
  146.      The 5th and 6th parameters are the selection start and end, respectively.
  147.      The return value is the new cursor position within the "newData"
  148.      parameter (zero based), unless it is negative, in which case:
  149.      -1 means the input is inconsistent (position not in range or not on a filter.
  150.      -2 means the input keystroke is not accepted by the relevant filter.
  151.   */
  152.   public int processKey(java.awt.event.KeyEvent e, int pos, String data,
  153.                         StringBuffer newData, int selStart, int selEnd) {
  154.     newData.append(data);    // init output to input
  155.     int keyCode = e.getKeyCode();
  156.     if (!setMaskPos(pos) && !isNavKey(keyCode))
  157.       return -1;   // cursor pos not in bounds
  158.     char key = e.getKeyChar();
  159.     if (key == _decimalPoint && _datatype == numbtype) {   // decimal?
  160.  
  161.       // Move caret to first filter to the right of first decimal point
  162.       // that is to the right of current caret position.
  163.       while (getNextMaskChar() != 0)
  164.         if (_curMaskChar == _decimalPoint)
  165.           return nextFilterPos();
  166.       return pos;   // could not find rightward decimal point
  167.     } else if (filtertype != _curMaskType) {   // not on filter position?
  168.       if (0 == pos && 0 == selStart && selEnd > _firstFilterPos)
  169.         setMaskPos(pos = _firstFilterPos);
  170.       else if (!isNavKey(keyCode)) {
  171.         if (pos > _lastFilterPos)
  172.           return -1;      // beyond last filter
  173.         nextFilterPos();  // advance to next filter
  174.         pos = _curMaskPos - _commandCorrection;
  175.       }
  176.     }
  177.     switch (keyCode) {   // handle control keys
  178.     case e.VK_BACK_SPACE:
  179.       if (selStart < selEnd) {  // if selected text exists
  180.         if (pos != selStart || selEnd - selStart > 1) {
  181.           clearSelectedText(selStart, selEnd, newData);
  182.           return Math.max(selStart, _firstFilterPos);
  183.         }
  184.       }
  185.       int newPos = prevFilterPos();
  186.       if (pos > 0 && newPos != -1)
  187.         newData.setCharAt(newPos, '_');
  188.       return newPos != -1 ? newPos : pos;
  189.     case e.VK_DELETE:
  190.       if (selStart < selEnd) {   // if selected text exists
  191.         clearSelectedText(selStart, selEnd, newData);
  192.         return Math.max(selStart, _firstFilterPos);
  193.       }
  194.       newData.setCharAt(pos, '_');
  195.       return pos;
  196.     case e.VK_LEFT:
  197.       return prevFilterPos();
  198.     case e.VK_RIGHT:
  199.       return nextFilterPos();
  200.     case e.VK_HOME:
  201.       return _filterPositions[0] - _commandCorrections[0];
  202.     case e.VK_END:
  203.       return _filterPositions[_filterCount-1] - _commandCorrections[_filterCount-1];
  204.     }
  205.     if (matchFilter(key)) {
  206.       clearSelectedText(selStart, selEnd, newData);
  207.       if      (eShiftLower == _shiftState)
  208.         key = Character.toLowerCase(key);
  209.       else if (eShiftUpper == _shiftState)
  210.         key = Character.toUpperCase(key);
  211.       newData.setCharAt(pos, key);
  212.       int newpos = nextFilterPos();;  // match: advance to next filter
  213.       return newpos == -1 ? _lastFilterPos - 999 : newpos;
  214.     } else              // no match: return with no changes
  215.       return -2;
  216.   }
  217.  
  218.   public boolean stripMask(String data, StringBuffer newData) {
  219.     if (_maskLength == 0){       
  220.       newData.append(data);
  221.       return true;
  222.     }
  223.     resetMaskScan();
  224.     boolean retval = true;
  225.     for (int i = 0; getNextMaskChar() != 0; i++) {
  226.       char c = data.charAt(i);
  227.       if (_curMaskType == filtertype) {
  228.         if (c != '_')
  229.           newData.append(c);
  230.         else if (isMandatory())  // empty filter position
  231.           retval = false;        // missing mandatory data
  232.       } else if (_curMaskChar == _decimalPoint && _datatype == numbtype) {
  233.         newData.append(_decimalPoint);
  234.       }
  235.     }
  236.     return retval;
  237.   }
  238.  
  239.   // Return true iff the engine handles the given key stroke.
  240.   public boolean isHandledKey(java.awt.event.KeyEvent e) {
  241.       if (0 == _maskLength)  // if no mask, let component handle all keys
  242.         return false;
  243.       char c = e.getKeyChar();
  244.       if (Character.isISOControl(c)) {
  245.         int k = e.getKeyCode();
  246.         switch (k) {
  247.         case e.VK_LEFT:
  248.         case e.VK_RIGHT:
  249.         case e.VK_HOME:
  250.         case e.VK_END:
  251.           return !e.isShiftDown();
  252.         }
  253.         switch (c) {
  254.         case e.VK_BACK_SPACE:
  255.         case e.VK_DELETE:
  256.           return true;
  257.         default:
  258.           return false;
  259.         }
  260.       }
  261.       return true;
  262.   }
  263.  
  264.   public String cut(String data, int selStart, int selEnd, StringBuffer newData) {
  265.     newData.append(data);
  266.     setMaskPos(selStart);
  267.     clearSelectedText(selStart, selEnd, newData);
  268.     return data.substring(selStart, selEnd);
  269.   }
  270.  
  271.   public int paste(String data, String pasteData, int pos, StringBuffer newData, int selStart, int selEnd) {
  272.     boolean hasSelection = selStart < selEnd;
  273.     if (hasSelection)
  274.       pos = selStart;
  275.     if (!setMaskPos(pos))
  276.       return -1;
  277.     StringBuffer sb = new StringBuffer(data);
  278.     int len = pasteData.length();
  279.     KeyEvent e = new KeyEvent(new TextField(), 0, 0, 0, 0);
  280.     String s = new String(sb.toString());
  281.     int newpos = pos;
  282.     for (int i = 0; i < len; i++) {
  283.       char c = pasteData.charAt(i);
  284.       e.setKeyChar(c);
  285.       e.setKeyCode(c);
  286.       sb = new StringBuffer();
  287.       int selstart = 0, selend = 0;
  288.       if (hasSelection && newpos >= selStart && newpos < selEnd) {
  289.         if (i == 0) {
  290.           selstart = selStart;
  291.           selend = selEnd;
  292.         } else {
  293.           selstart = newpos;
  294.           selend = newpos + 1;
  295.         }
  296.       }
  297.       newpos = processKey(e, newpos, s, sb, selstart, selend);
  298.       if (newpos == -1 || newpos == -2) {
  299.         break;  // next paste character was rejected by mask
  300.       }
  301.       s = sb.toString();
  302.     }
  303.     newData.append(s);
  304.     return newpos;
  305.   }
  306.  
  307.   boolean clearSelectedText(int selStart, int selEnd, StringBuffer newData) {
  308.     if (selEnd > selStart) {  // only if selected text exists
  309.       // save mask scan state
  310.       int curMaskPos = _curMaskPos;
  311.       int nextMaskPos = _nextMaskPos;
  312.       int curMaskType = _curMaskType;
  313.       char curMaskChar = _curMaskChar;
  314.       char lastMaskChar = _lastMaskChar;
  315.       char decimalPoint = _decimalPoint;
  316.       int commandCorrection = _commandCorrection;
  317.       int shiftState = _shiftState;
  318.  
  319.       setMaskPos(selStart);
  320.       int outpos = selStart;   // track input position
  321.       char c = _curMaskChar;
  322.       while (outpos < selEnd && c != 0) {
  323.         if (_curMaskType == filtertype)
  324.           c = '_';
  325.         newData.setCharAt(outpos++, c);
  326.         c = getNextMaskChar();
  327.       }
  328.  
  329.       // restore mask scan state
  330.       _curMaskPos = curMaskPos;
  331.       _nextMaskPos = nextMaskPos;
  332.       _curMaskType = curMaskType;
  333.       _curMaskChar = curMaskChar;
  334.       _lastMaskChar = lastMaskChar;
  335.       _decimalPoint = decimalPoint;
  336.       _commandCorrection = commandCorrection;
  337.       _shiftState = shiftState;
  338.       return true;
  339.     }
  340.     return false;
  341.   }
  342.  
  343.   // Set new position in mask.  Return false iff it is out of bounds.
  344.   boolean setMaskPos(int pos) {
  345.     if (pos < 0)
  346.       return false;
  347.     resetMaskScan();
  348.     _inRange = true;
  349.     while (pos-- >= 0)
  350.       if (getNextMaskChar() == 0)
  351.         return _inRange = false;
  352.     return true;
  353.   }
  354.  
  355.   // Returns the position in the mask of the first filter that follows the
  356.   // current position.
  357.   int nextFilterPos() {
  358.     while (getNextMaskChar() != 0)
  359.       if (_curMaskType == filtertype)
  360.         return _curMaskPos - _commandCorrection;
  361.     return -1;
  362.   }
  363.  
  364.   // Returns the position in the mask of the filter that precedes the
  365.   // current position.  Return of -1 means no previous filter was found.
  366.   int prevFilterPos() {
  367.     int oldPos = _curMaskPos;
  368.     resetMaskScan();
  369.     int prevFilterPos = -1;
  370.     int prevCommandCorrection = 0;
  371.     while (nextFilterPos() != -1) {
  372.       if (_curMaskPos >= oldPos) { // scanned past previous filter
  373.         if (!_inRange)
  374.           return _curMaskPos - _commandCorrection;
  375.         break;
  376.       }
  377.       prevFilterPos = _curMaskPos;  // save pos info on this filter
  378.       prevCommandCorrection = _commandCorrection;
  379.     }
  380.     return prevFilterPos - prevCommandCorrection;
  381.   }
  382.  
  383.   // Return true iff key is accepted by current filter
  384.   boolean matchFilter(char key) {
  385.     switch (_curMaskChar) {
  386.     case '0':   // any digit
  387.       return Character.isDigit(key);
  388.     case '9':   // any digit or space
  389.       return Character.isDigit(key) || Character.isSpaceChar(key);
  390.     case '#':   // digit, space or sign
  391.       return Character.isDigit(key) || Character.isSpaceChar(key) ||
  392.              key == '+' || key == '-';
  393.     case 'L':   // letter
  394.     case '?':
  395.       return Character.isLetter(key);
  396.     case 'A':   // letter or digit
  397.     case 'a':
  398.       return Character.isLetterOrDigit(key);
  399.     case '&':   // any character
  400.     case 'C':
  401.       return true;
  402.     default:
  403.       return false;
  404.     }
  405.   }
  406.  
  407.   // Prepare mask to be scanned from the beginning.
  408.   void resetMaskScan() {
  409.     _curMaskPos = 0;
  410.     _nextMaskPos = 0;   // set first char to get
  411.     _lastMaskChar = 0;
  412.     _commandCorrection = 0;
  413.     _curMaskType = 0;
  414.     _shiftState = eShiftNul;
  415.   }
  416.  
  417.   char getNextMaskChar() {
  418.     while (true) {        // loop through command chars
  419.       if (_nextMaskPos >= _maskLength)
  420.         return 0;
  421.       _lastMaskChar = _curMaskChar;
  422.       _curMaskChar = _mask.charAt(_nextMaskPos++);
  423.       if (isCommand()) {
  424.         _commandCorrection++;
  425.         if ('<' == _curMaskChar)
  426.           _shiftState = eShiftLower;
  427.         else if ('>' == _curMaskChar)
  428.           _shiftState = eShiftUpper;
  429.       } else
  430.         break;
  431.     }
  432.     _curMaskType = literaltype;
  433.     if (_lastMaskChar != '\\' && isFilter())
  434.       _curMaskType = filtertype;
  435.     _curMaskPos = _nextMaskPos - 1;
  436.     return _curMaskChar;
  437.   }
  438.  
  439.   // Return true iff key is a cursor navigation key.
  440.   boolean isNavKey(int key) {
  441.     switch (key) {
  442.     default:
  443.       return false;
  444.     case java.awt.event.KeyEvent.VK_LEFT:
  445.     case java.awt.event.KeyEvent.VK_RIGHT:
  446.     case java.awt.event.KeyEvent.VK_BACK_SPACE:
  447.     case java.awt.event.KeyEvent.VK_HOME:
  448.     case java.awt.event.KeyEvent.VK_END:
  449.     }
  450.     return true;
  451.   }
  452.  
  453.   boolean isFilter()    { return -1 != "09#aAL?&C".indexOf(_curMaskChar); }
  454.   boolean isCommand()   { return -1 != "<>\\"     .indexOf(_curMaskChar); }
  455.   boolean isMandatory() { return -1 != "0LA&"     .indexOf(_curMaskChar); }
  456.  
  457.   // Called by all ctors
  458.   void init() {}
  459.  
  460.   // Variables
  461.   String  _mask             = ""      ; // current mask
  462.   int     _maskLength       = 0       ; // its length
  463.   int     _datatype         = texttype; // data type of masked field
  464.   int     _filterCount      = 0       ; // count of filters in mask
  465.   int     _maskCount        = 0       ; // count of displayable characters
  466.   int     _firstFilterPos   = 0       ;
  467.   int     _lastFilterPos    = 0       ;
  468.   int[]   _filterPositions            ; // track all filter positions
  469.   int[]   _commandCorrections         ; // track all command corrections
  470.   boolean _inRange          = true    ; // true if last mask position request is in range
  471.  
  472.   // State variables for scanning the mask
  473.   int  _curMaskPos        = 0;          // position of current mask character
  474.   int  _nextMaskPos       = 0;          // position of next mask character
  475.   int  _curMaskType       = nulltype;   // type of current mask character
  476.   char _curMaskChar       = 0;
  477.   char _lastMaskChar      = 0;
  478.   int  _commandCorrection = 0;
  479.   int  _shiftState        = eShiftNul;
  480.   char _decimalPoint      = '.';
  481.  
  482.   // Internal mask command type codes
  483.   static final int nulltype    = 0;
  484.   static final int filtertype  = 1;
  485.   static final int literaltype = 2;
  486.  
  487.   // Internal shift states
  488.   static final int eShiftNul   = 0;
  489.   static final int eShiftUpper = 1;
  490.   static final int eShiftLower = 2;
  491. }
  492.